package parser;

import java.util.ArrayList;

import scanner.LexicalException;
import scanner.Scanner;
import scanner.Token;
import util.AST.AST;
import util.AST.Program;
import util.AST.AsgComd.AsgComd;
import util.AST.AsgComd.AsgComdExp;
import util.AST.AsgComd.AsgComdFuncCall;
import util.AST.Comand.Comand;
import util.AST.Comand.FunctionDeclaration;
import util.AST.Comand.ProcedureDeclaration;
import util.AST.Comand.VarDeclaration;
import util.AST.Expression.BinaryExpression;
import util.AST.Expression.Expression;
import util.AST.Expression.IdUnaryExpression;
import util.AST.Expression.NumberUnaryExpression;
import util.AST.Statement.AssignStatement;
import util.AST.Statement.ExitStatement;
import util.AST.Statement.FunctionCallStatement;
import util.AST.Statement.IfElseStatement;
import util.AST.Statement.PutLineStatement;
import util.AST.Statement.ReturnStatement;
import util.AST.Statement.Statement;
import util.AST.Statement.WhileStatement;
import util.AST.Terminal.ID;
import util.AST.Terminal.Number;
import util.AST.Terminal.Operator;
import util.AST.Terminal.Tipo;

/**
 * Parser class
 * @version 2010-august-29
 * @discipline Projeto de Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class Parser {

	// The current token
	private Token currentToken = null;
	// The scanner
	private Scanner scanner = null;
	
	private ArrayList<Comand> comands;
	
	/**
	 * Parser constructor
	 */
	public Parser() {
		// Initializes the scanner object
		this.scanner = new Scanner();
		try {
			this.currentToken = this.scanner.getNextToken();
		} catch (LexicalException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Veririfes if the current token kind is the expected one
	 * @param kind
	 * @throws SyntacticException
	 * @throws LexicalException 
	 */ 
		// If the current token kind is equal to the expected
		// Gets next token
		// If not
		// Raises an exception
	private void accept(int kind) throws SyntacticException, LexicalException {
		if(kind == currentToken.getKind())
			currentToken = scanner.getNextToken();
		else
			throw new SyntacticException(null, currentToken);
		System.out.println(currentToken.getKind()+" - "+currentToken.getSpelling());
	}
	
	/**
	 * Gets next token
	 * @throws LexicalException 
	 */ 
	private void acceptIt() throws LexicalException {
		currentToken = scanner.getNextToken();
		System.out.println(currentToken.getKind()+" - "+currentToken.getSpelling());
	}

	/**
	 * Verifies if the source program is syntactically correct
	 * @throws SyntacticException
	 * @throws LexicalException 
	 */ 
	public AST parse() throws SyntacticException, LexicalException{
		AST ast = parseProgram();
		accept(GrammarSymbols.EOT);
		return ast;
		
	}
	
	//VarDeclaration ::= id (, id)* : [int | float]
	private VarDeclaration parseVarDeclaration () throws LexicalException, SyntacticException {
		VarDeclaration cmd;
		ArrayList<ID> ides = new ArrayList<ID>();
        ID id = new ID(this.currentToken);
        ides.add(id);
        accept(GrammarSymbols.ID);
        while(this.currentToken.getKind() == GrammarSymbols.COMMA){
                acceptIt();
                id = new ID(this.currentToken);
                ides.add(id);
                accept(GrammarSymbols.ID);                      
        }
		accept(GrammarSymbols.COLON);
		Tipo tp;
		if(currentToken.getKind() == GrammarSymbols.INT || currentToken.getKind() == GrammarSymbols.FLOAT ){
			tp = new Tipo(currentToken);	
			acceptIt();
		}else
			throw new SyntacticException(null, currentToken);
		cmd = new VarDeclaration(ides, tp);
		return cmd;
		
	}
	
	private boolean isArithimeticMultOperator(){
			 return (currentToken.getKind() == GrammarSymbols.MULT || currentToken.getKind() == GrammarSymbols.DIV);
	}
	
	private boolean isArithimeticAddOperator(){
		 return (currentToken.getKind() == GrammarSymbols.PLUS || currentToken.getKind() == GrammarSymbols.MINUS);
	}
	
	private boolean isRelationalOperator(){
		return (currentToken.getKind() == GrammarSymbols.GREATERTHAN
		|| currentToken.getKind() == GrammarSymbols.LESSERTHAN
		|| currentToken.getKind() == GrammarSymbols.GREATERTHANEQUAL
		|| currentToken.getKind() == GrammarSymbols.LESSERTHANEQUAL
		|| currentToken.getKind() == GrammarSymbols.EQUAL
		|| currentToken.getKind() == GrammarSymbols.NOTEQUAL);
	}
	
	
	//FunctionCall ::= $ id ([ id [; id]* ]* )
	private FunctionCallStatement parseFunctionCall () throws SyntacticException, LexicalException {
		FunctionCallStatement fcs;
		ID id;
		accept(GrammarSymbols.CURRENCY);
		if(currentToken.getKind() == GrammarSymbols.ID){
			id = new ID(currentToken);
			acceptIt();
		}else
			throw new SyntacticException(null, currentToken);
		if(currentToken.getKind() == GrammarSymbols.LPAR){
			acceptIt();
			ArrayList<ID> ideses = parseParameters();
			accept(GrammarSymbols.RPAR);
			if(ideses != null && !ideses.isEmpty())
				fcs = new FunctionCallStatement(id, ideses);
			else
				fcs = new FunctionCallStatement(id);
		}else{
			fcs = new FunctionCallStatement(id);
		}
		
		return fcs;
	}

	//ExpRel ::= ExpAritAdd opRel ExpAritAdd
	private Expression parseExpRel () throws LexicalException, SyntacticException{
		Expression exp;
		Operator op = null;
		Expression left = parseExpAritAdd();
		Expression right = null;
		while(isRelationalOperator()) {
			op = new Operator(currentToken);
            acceptIt();
            right = parseExpAritAdd();
            exp = new BinaryExpression(left, op, right);
            left = exp;
		}
		
		exp = new BinaryExpression(left, op, right);
		return exp;
	}
	
	private AsgComd parseAsgComd () throws SyntacticException, LexicalException{
		AsgComd ac = null;
		if(currentToken.getKind() == GrammarSymbols.CURRENCY){
			FunctionCallStatement fcs = parseFunctionCall();
			ac = new AsgComdFuncCall(fcs);
		}else{
			Expression exp = parseExpAritAdd();
			ac = new AsgComdExp(exp);
		}
		return ac;
	}
	 private ArrayList<ID> parseParameters() throws SyntacticException, LexicalException{
         ArrayList<ID> ides = new ArrayList<ID>();
         ID id = new ID(this.currentToken);
         ides.add(id);
         accept(GrammarSymbols.ID);
         while(this.currentToken.getKind() == GrammarSymbols.SEMICOLON){
                 acceptIt();
                 id = new ID(this.currentToken);
                 ides.add(id);
                 accept(GrammarSymbols.ID);                      
         }
         
         return ides;
 }
	
	//Statement ::= 	id := [ ExpAritAdd | FunctionCall ] ;
	// 					| Put_Line ( id ) ;
	// 					| exit;
	// 					| FunctionCall
	private Statement parseStatement () throws LexicalException, SyntacticException{
		Statement stmt = null;
		if(currentToken.getKind() == GrammarSymbols.ID){
			ID id = new ID(currentToken);
			acceptIt();
			accept(GrammarSymbols.ASG);
			AsgComd ac = parseAsgComd();
			stmt = new AssignStatement(id, ac);
			accept(GrammarSymbols.SEMICOLON);
		}else if(currentToken.getKind() == GrammarSymbols.PUT_LINE){
			acceptIt();
			accept(GrammarSymbols.LPAR);
			ID id = new ID(currentToken);
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.RPAR);
			stmt = new PutLineStatement(id);
			accept(GrammarSymbols.SEMICOLON);
		}else if(currentToken.getKind() == GrammarSymbols.EXIT){
			acceptIt();
			stmt = new ExitStatement();
			accept(GrammarSymbols.SEMICOLON);
		}else if(currentToken.getKind() == GrammarSymbols.CURRENCY){
			stmt = parseFunctionCall();
			accept(GrammarSymbols.SEMICOLON);
		}
			
		else{
			throw new SyntacticException(null, currentToken);
		}
		return stmt;
	}
	
	//	FunctionStmt ::= Statement 
	//	| return ExpAritAdd ;
	//	| if ExpRel then [FunctionStmt]* [else [FunctionStmt]*]? end if ;
	//	| while ExpRel loop [FunctionStmt]* end loop ;
	private Statement parseFunctionStmt() throws SyntacticException, LexicalException{
		Statement stmt;
		
		if(currentToken.getKind() == GrammarSymbols.IF){
			acceptIt();
			Expression expression = parseExpRel();
			accept(GrammarSymbols.THEN);
			ArrayList<Statement> ifStmts = new ArrayList<Statement>();
			ArrayList<Statement> elseStmts = new ArrayList<Statement>();
			ifStmts = hasStatementsFunc();
			if(currentToken.getKind() == GrammarSymbols.ELSE){
				acceptIt();
				elseStmts = hasStatementsFunc();
			}
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.IF);
			accept(GrammarSymbols.SEMICOLON);
			stmt = new IfElseStatement(expression, ifStmts, elseStmts);
		}else if(currentToken.getKind() == GrammarSymbols.WHILE){
			acceptIt();
			Expression exp = parseExpRel();
			accept(GrammarSymbols.LOOP);
			ArrayList<Statement> whileStmts = new ArrayList<Statement>();
			whileStmts = hasStatementsFunc();
			
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.LOOP);
			accept(GrammarSymbols.SEMICOLON);
			stmt = new WhileStatement(exp, whileStmts);
		}else if(currentToken.getKind() == GrammarSymbols.RETURN){
			acceptIt();
			Expression exp = parseExpAritAdd();
			accept(GrammarSymbols.SEMICOLON);
			stmt = new ReturnStatement(exp);
		}else{
			stmt = parseStatement();
		}
		return stmt;
	}
	
	
	private Statement parseProcedureStmt () throws LexicalException, SyntacticException{
		Statement stmt = null;
	//	 | if ExpRel then [ProcedureStmt]* [else [ProcedureStmt]*]? end if ;
		if(currentToken.getKind() == GrammarSymbols.IF){
			acceptIt();
			Expression expression = parseExpRel();
			accept(GrammarSymbols.THEN);
			ArrayList<Statement> ifStmts = new ArrayList<Statement>();
			ArrayList<Statement> elseStmts = new ArrayList<Statement>();
			ifStmts = hasStatementsProc();
			if(currentToken.getKind() == GrammarSymbols.ELSE){
				acceptIt();
				elseStmts = hasStatementsProc();
			}
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.IF);
			accept(GrammarSymbols.SEMICOLON);
			stmt = new IfElseStatement(expression, ifStmts, elseStmts);
	//	| while ExpRel loop [ProcedureStmt]* end loop ;
		}else if(currentToken.getKind() == GrammarSymbols.WHILE){
			acceptIt();
			Expression exp = parseExpRel();
			accept(GrammarSymbols.LOOP);
			ArrayList<Statement> whileStmts = new ArrayList<Statement>();
			whileStmts = hasStatementsProc();
			
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.LOOP);
			accept(GrammarSymbols.SEMICOLON);
			stmt = new WhileStatement(exp, whileStmts);
	
	//	| return ; 
		}else if(currentToken.getKind() == GrammarSymbols.RETURN){
			acceptIt();
			accept(GrammarSymbols.SEMICOLON);
			stmt = new ReturnStatement();
	//	| ProcedureStmt ::= Statement 
		}else{
			stmt = parseStatement();
		}
		return stmt;
	}
	
	//function id ([VarDeclaration [; VarDeclaration]* ]?) return [int | float] 
	//is [VarDeclaration ;]* begin [FunctionStmt]*  end id ; 
	private FunctionDeclaration parseFuntionDeclaration () throws SyntacticException, LexicalException{
		FunctionDeclaration cmd;
		ID id = null;
		Tipo tp = null;
		ArrayList<VarDeclaration> varDecs = new ArrayList<VarDeclaration>();
		ArrayList<VarDeclaration> args = new ArrayList<VarDeclaration>();
		ArrayList<Statement> stmts = new ArrayList<Statement>();
		
		accept(GrammarSymbols.FUNCTION);
		if(currentToken.getKind() == GrammarSymbols.ID){
			id = new ID(currentToken);
			acceptIt();
		}
		accept(GrammarSymbols.LPAR);
		if(currentToken.getKind() == GrammarSymbols.ID){
			VarDeclaration vd = parseVarDeclaration();
	        args.add(vd);
	        while(this.currentToken.getKind() == GrammarSymbols.SEMICOLON){
	                acceptIt();
	                vd = parseVarDeclaration();
	                args.add(vd);
	         }
		}
		accept(GrammarSymbols.RPAR);
		
		accept(GrammarSymbols.RETURN);
		if(currentToken.getKind() == GrammarSymbols.INT || currentToken.getKind() == GrammarSymbols.FLOAT){
			tp = new Tipo(currentToken);
			acceptIt();
		}
		accept(GrammarSymbols.IS);
		while (currentToken.getKind() != GrammarSymbols.BEGIN){
			VarDeclaration vD = parseVarDeclaration();
			varDecs.add(vD);
			acceptIt();
		}
		accept(GrammarSymbols.BEGIN);
		while (currentToken.getKind() != GrammarSymbols.END){
			Statement s = parseFunctionStmt();
			stmts.add(s);
		}
		accept(GrammarSymbols.END);
		accept(GrammarSymbols.ID);
		accept(GrammarSymbols.SEMICOLON);
		if(args != null && !args.isEmpty())
			cmd = new FunctionDeclaration(id, tp, args, varDecs, stmts);
		else
			cmd = new FunctionDeclaration(id, tp, varDecs, stmts);
		
		return cmd;
	}
		//procedure id [( VarDeclaration [; VarDeclaration]* )]? 
		//	is [FunctionDeclaration | ProcedureDeclaration]* [VarDeclaration ;]* begin [ProcedureStmt]* end id;
	private ProcedureDeclaration parseProcedureDeclaration() throws SyntacticException, LexicalException{
		
		ProcedureDeclaration cmd;
		ID id = null;
		ArrayList<VarDeclaration> args = null;
		ArrayList<VarDeclaration> varDecs = new ArrayList<VarDeclaration>();
		ArrayList<Statement> stmts = new ArrayList<Statement>();
		ArrayList<ProcedureDeclaration> procedures = new ArrayList<ProcedureDeclaration>();
		ArrayList<FunctionDeclaration> functions = new ArrayList<FunctionDeclaration>();
		
		 accept(GrammarSymbols.PROCEDURE);
		 if(currentToken.getKind() == GrammarSymbols.ID){
				id = new ID(currentToken);
				acceptIt();
			}
			if(currentToken.getKind() == GrammarSymbols.LPAR){
				acceptIt();
				if(currentToken.getKind() == GrammarSymbols.ID){
					args = new ArrayList<VarDeclaration>();
					VarDeclaration vd = parseVarDeclaration();
					args.add(vd);
					while(this.currentToken.getKind() == GrammarSymbols.SEMICOLON){
						acceptIt();
						vd = parseVarDeclaration();
						args.add(vd);
					}
				}
				
			accept(GrammarSymbols.RPAR);
			}
			accept(GrammarSymbols.IS);
			while (currentToken.getKind() == GrammarSymbols.FUNCTION || currentToken.getKind() == GrammarSymbols.PROCEDURE){
				if(currentToken.getKind() == GrammarSymbols.FUNCTION){
					FunctionDeclaration fd = parseFuntionDeclaration();
					functions.add(fd);
				}else{
					ProcedureDeclaration pd = parseProcedureDeclaration();
					procedures.add(pd);
				}
			}
			
			while (currentToken.getKind() != GrammarSymbols.BEGIN){
				VarDeclaration vD = parseVarDeclaration();
				varDecs.add(vD);
				accept(GrammarSymbols.SEMICOLON);
			}
			
			accept(GrammarSymbols.BEGIN);

			while (currentToken.getKind() != GrammarSymbols.END){
				Statement s = parseProcedureStmt();
				stmts.add(s);
			}
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.SEMICOLON);
			if(args != null && !args.isEmpty())
				cmd = new ProcedureDeclaration(id, args, functions, procedures, varDecs, stmts);
			else
				cmd = new ProcedureDeclaration(id, functions, procedures, varDecs, stmts);
			
			return cmd;
	}
	
	//ExpAritAdd ::= ExpAritMult [opAritAdd ExpAritMult]*
	private Expression parseExpAritAdd () throws LexicalException, SyntacticException{
		Expression exp = null;
		
		Expression left = parseExpAritMult();
		Operator op = null ;
		Expression right = null;
		exp = new BinaryExpression(left, op, right);
		while(isArithimeticAddOperator()){
			op = new Operator(currentToken);
			acceptIt();
			right = parseExpAritMult();
			exp = new BinaryExpression(left, op, right);
			left = exp;
		}
		
		return exp;
	}
	
	//ExpAritMult ::= ExpPar [opAritMult ExpPar]]*
	private Expression parseExpAritMult () throws LexicalException, SyntacticException{
		Expression exp = null;
		
		Expression right = null;
		Expression left = parseExpPar();
		Operator op = null ;
		exp = new BinaryExpression(left, op, right);
		while(isArithimeticMultOperator()){
			op = new Operator(currentToken);
			acceptIt();
			right = parseExpPar();
			exp = new BinaryExpression(left, op, right);
			left = exp;
		}
		
		return exp;
	}
	
	//ExpPar ::= (ExpAritAdd) | ID | NUMBER
	private Expression parseExpPar() throws SyntacticException, LexicalException{
		Expression exp;
		if(currentToken.getKind() == GrammarSymbols.LPAR){
			acceptIt();
			exp =  parseExpAritAdd();
			accept(GrammarSymbols.RPAR);
		}else if(currentToken.getKind() == GrammarSymbols.ID) {
			ID id = new ID(currentToken);
			exp = new IdUnaryExpression(id);
			acceptIt();
		}else if(currentToken.getKind() == GrammarSymbols.NUMBER){
			Number num = new Number(currentToken);
			exp = new NumberUnaryExpression(num);
			acceptIt();
		}else{
			throw new SyntacticException(null, currentToken);
		}
		return exp;
	}
	
	
	//Program ::= (ProcedureDeclaration)* eot
	private Program parseProgram() throws SyntacticException, LexicalException{
		 
		comands = new ArrayList<Comand>();
		ProcedureDeclaration pd = null;
		while(currentToken.getKind()!= GrammarSymbols.EOT){
			
			pd = parseProcedureDeclaration();
			comands.add(pd);
		}
		Program prog = new Program(comands);
		return prog;
	}
	
	private ArrayList<Statement> hasStatementsFunc () throws SyntacticException, LexicalException{
		ArrayList<Statement> sts = new ArrayList<Statement>();
		
		while(currentToken.getKind() == GrammarSymbols.ID
		|| currentToken.getKind() == GrammarSymbols.WHILE
		|| currentToken.getKind() == GrammarSymbols.IF
		|| currentToken.getKind() == GrammarSymbols.PUT_LINE
		|| currentToken.getKind() == GrammarSymbols.EXIT
		|| currentToken.getKind() == GrammarSymbols.RETURN
		|| currentToken.getKind() == GrammarSymbols.CURRENCY){
			Statement st = parseFunctionStmt();
			sts.add(st);
		}
		return sts;
	}
	private ArrayList<Statement> hasStatementsProc () throws LexicalException, SyntacticException{
		ArrayList<Statement> sts = new ArrayList<Statement>();
		
		while(currentToken.getKind() == GrammarSymbols.ID
		|| currentToken.getKind() == GrammarSymbols.WHILE
		|| currentToken.getKind() == GrammarSymbols.IF
		|| currentToken.getKind() == GrammarSymbols.PUT_LINE
		|| currentToken.getKind() == GrammarSymbols.EXIT
		|| currentToken.getKind() == GrammarSymbols.RETURN
		|| currentToken.getKind() == GrammarSymbols.CURRENCY){
			Statement st = parseProcedureStmt();
			sts.add(st);
		}
		return sts;
	}
	
	
	
}
